package jp.wug.core;

import com.google.inject.TypeLiteral;
import jp.wug.Renderer;
import jp.wug.WugRequest;
import jp.wug.util.NamedObjects;
import jp.wug.util.TypeLiteralUtil;
import org.w3c.dom.Element;

import javax.inject.Provider;
import java.util.*;

class ControllerFactory {
    private final ModelFactory modelFactory;
    private final Map<TypeAndKey, PathItem> map = new HashMap<TypeAndKey, PathItem>();
    private final NamedObjects<Object> keys = new NamedObjects<Object>();

    ControllerFactory(ModelFactory modelFactory) {
        this.modelFactory = modelFactory;
    }

    <T> String set(Class<T> type, T value, PackedExpression<Model> expression, String label) {
        PathItem<T> pathItem = new PathItem<T>(value, expression, expression.getArgCount());
        String key = keys.giveName(pathItem, label);
        TypeAndKey typeAndKey = new TypeAndKey(type, key);
        assert !map.containsKey(typeAndKey);
        map.put(typeAndKey, pathItem);
        return key;
    }

    boolean contains(Class<?> type, String keyString) {
        TypeAndKey key = new TypeAndKey(type, keyString);
        return map.containsKey(key);
    }

    @SuppressWarnings("unchecked")
    <T> PathItem<T> get(Class<T> type, String keyString) {
        TypeAndKey key = new TypeAndKey(type, keyString);
        return (PathItem<T>) map.get(key);
    }

    <T> Set<String> getAllKeys(Class<T> type) {
        Set<String> keysSet = new HashSet<String>();
        for (TypeAndKey k : map.keySet()) {
            if (k.getType().equals(type)) {
                keysSet.add(k.getKey());
            }
        }
        return keysSet;
    }

    void init(PageFactory pageFactory) {
        for (String key : getAllKeys(Import.class)) {
            Import anImport = get(Import.class, key).get();
            anImport.resolve(pageFactory);
        }
    }

    private static class TypeAndKey {
        private final Class<?> type;
        private final String key;

        TypeAndKey(Class<?> type, String key) {
            this.type = type;
            this.key = key;
        }

        public String getKey() {
            return key;
        }

        public Class<?> getType() {
            return type;
        }

        @Override
        public String toString() {
            return "TypeAndKey{" + "type=" + type + ", key='" + key + '\'' + '}';
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof TypeAndKey) {
                TypeAndKey typeAndKey = (TypeAndKey) object;
                if (getKey().equals(typeAndKey.getKey()) && getType().equals(typeAndKey.getType())) {
                    return true;
                }
            }
            return false;
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }
    }

    ControllerBuilder createPathBuilder(PageFactory.Builder builder) {
        return new ControllerBuilder(builder, this, modelFactory);
    }

    private static class PathItem<T> {
        private final T t;
        private final PackedExpression<Model> expression;
        private final int argumentCount;

        PathItem(T t, PackedExpression<Model> expression, int argumentCount) {
            this.t = t;
            this.expression = expression;
            this.argumentCount = argumentCount;
        }

        public T get() {
            return t;
        }

        PathAndArg<Model> createPathAndArg(PathAndArg<Model> pathAndArg, List<String> strings) {
            List<String> args = strings.subList(1, argumentCount + 1);
            return pathAndArg.createChild(expression, args);
        }

        PathAndArg<Model> createSibling(PathAndArg<Model> pathAndArg, List<String> strings) {
            List<String> args = strings.subList(1, argumentCount + 1);
            return pathAndArg.createSibling(expression, args);
        }

        List<String> getNextArgs(List<String> strings) {
            return strings.subList(argumentCount + 1, strings.size());
        }
    }

    private static class LabeledObject<T> {
        private final String label;
        private final T t;

        private LabeledObject(String label, T t) {
            this.label = label;
            this.t = t;
        }

        protected T get() {
            return t;
        }

        @Override
        public String toString() {
            return label + "{" + t + '}';
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof LabeledObject) {
                LabeledObject that = (LabeledObject) object;
                return label.equals(that.label) && t.equals(that.t);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }
    }

    private static class Form extends LabeledObject<Expression<Void, Model>> {
        private final NamedObjects<Object> keys = new NamedObjects<Object>();
        private final Map<TypeAndKey, PathItem> map = new HashMap<TypeAndKey, PathItem>();

        private Form(ModelFactory modelFactory) {
            super("Form", new ThisExpression<Void>(modelFactory));
        }

        <T> void set(Class<T> type, T value, PackedExpression<Model> expression, String keyString) {
            TypeAndKey key = new TypeAndKey(type, keyString);
            assert !map.containsKey(key);
            map.put(key, new PathItem<T>(value, expression, expression.getArgCount()));
        }

        boolean contains(Class<?> type, String keyString) {
            TypeAndKey key = new TypeAndKey(type, keyString);
            return map.containsKey(key);
        }

        <T> PathItem<T> get(Class<T> type, String keyString) {
            TypeAndKey key = new TypeAndKey(type, keyString);
            assert map.containsKey(key);
            @SuppressWarnings("unchecked")
            PathItem<T> result = (PathItem<T>) map.get(key);
            return result;
        }

        <T> Set<String> getAllKeys(Class<T> type) {
            Set<String> keysSet = new HashSet<String>();
            for (TypeAndKey k : map.keySet()) {
                if (k.getType().equals(type)) {
                    keysSet.add(k.getKey());
                }
            }
            return keysSet;
        }

        String putPost(PackedExpression<Model> retrieve, Expression<Void, Void> expression, String label) {
            Post post = new Post(expression);
            String key = keys.giveName(post, label);
            set(Post.class, post, retrieve, key);
            return key;
        }

        String putControl(PackedExpression<Model> retrieve, Expression<Void, String[]> getter, Expression<String[], Void> setter, String label) {
            Control control = new Control(getter, setter);
            String key = keys.giveName(control, label);
            set(Control.class, control, retrieve, key);
            return key;
        }

        String[] listPostKeys() {
            Set<String> keys = getAllKeys(Post.class);
            return keys.toArray(new String[keys.size()]);
        }

        String[] listControlKeys() {
            Set<String> keys = getAllKeys(Control.class);
            return keys.toArray(new String[keys.size()]);
        }
    }

    private static class Action extends LabeledObject<Expression<Void, Void>> {
        private Action(Expression<Void, Void> action) {
            super("Action", action);
        }

        void action(Model model) {
            get().call(model, null);
        }
    }

    private static class Post extends LabeledObject<Expression<Void, Void>> {
        private Post(Expression<Void, Void> post) {
            super("Post", post);
        }

        public Expression<Void, Void> getPostExpression() {
            return get();
        }
    }

    private static class Control extends LabeledObject<Expression<Void, String[]>> {
        private final Expression<Void, String[]> getter;
        private final Expression<String[], Void> setter;

        private Control(Expression<Void, String[]> getter, Expression<String[], Void> setter) {
            super("Control", getter);
            this.getter = getter;
            this.setter = setter;
        }

        public Expression<Void, String[]> getGetter() {
            return getter;
        }

        public Expression<String[], Void> getSetter() {
            return setter;
        }
    }

    private static class Import extends LabeledObject<Class<?>> {
        private ControllerFactory exportedControllerFactory;

        Import(Class<?> type) {
            super("Import", type);
        }

        void resolve(PageFactory pageFactory) {
            exportedControllerFactory = pageFactory.getExportedControllerFactory(get());
        }

        public Class<?> getType() {
            return get();
        }
    }

    private static class Catch {
        private final ModelFactory modelFactory;
        private final TypeLiteral<?> exceptionType;
        private final List<String> pathStrings;

        private Catch(ModelFactory modelFactory, TypeLiteral<?> exceptionType, List<String> pathStrings) {
            this.modelFactory = modelFactory;
            this.exceptionType = exceptionType;
            this.pathStrings = pathStrings;
        }

        Expression<Void, Model> getExpression() {
            return new ExceptionGetter(modelFactory, pathStrings);
        }

        public String getLabel() {
            StringBuffer buffer = new StringBuffer();
            buffer.append("wugCatchKey");
            for (String s : pathStrings) {
                buffer.append(".");
                buffer.append(s);
            }
            return buffer.toString();
        }

        @Override
        public String toString() {
            return "Catch{" + exceptionType + "@" + pathStrings + '}';
        }

        @Override
        public boolean equals(Object object) {
            if (object instanceof Catch) {
                Catch that = (Catch) object;
                return exceptionType.equals(that.exceptionType) && pathStrings.equals(that.pathStrings);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }

        private static class ExceptionGetter implements Expression<Void, Model> {
            ModelFactory modelFactory;
            List<String> strings;

            private ExceptionGetter(ModelFactory modelFactory, List<String> strings) {
                this.modelFactory = modelFactory;
                this.strings = strings;
            }

            public Model call(Model model, Void aVoid) {
                throw new UnsupportedOperationException();
            }

            public Model tryCall(Model model, Void aVoid) throws ModelException {
                throw new UnsupportedOperationException();
            }

            public ModelFactory getModelFactory() {
                return modelFactory;
            }

            public List<String> asStrings() {
                return strings;
            }
        }
    }

    private static class ElementId extends LabeledObject<Expression<Void, ?>> {
        private ElementId(Expression<Void, ?> expression) {
            super("ElementId", expression);
        }
    }

    private static class Each extends LabeledObject<TypeLiteral<?>> {
        private Each(TypeLiteral<?> valueType) {
            super("Each", valueType);
        }
    }

    private static class ThisExpression<T> implements Expression<T, Model> {
        private final ModelFactory modelFactory;

        private ThisExpression(ModelFactory modelFactory) {
            if (modelFactory == null) {
                throw new AssertionError();
            }
            this.modelFactory = modelFactory;
        }

        public Model call(Model model, T t) {
            return modelFactory.create(model.getValue());
        }

        public Model tryCall(Model model, T t) {
            return call(model, t);
        }

        public ModelFactory getModelFactory() {
            return modelFactory;
        }

        public List<String> asStrings() {
            return Collections.emptyList();
        }

        @Override
        public String toString() {
            return "";
        }
    }

    private static class ExpressionImpl<T, U> implements Expression<T, U> {
        private final Expression<Void, Model> parent;
        private final Expression<T, U> expression;

        ExpressionImpl(Expression<Void, Model> parent, Expression<T, U> expression) {
            this.parent = parent;
            this.expression = expression;
        }

        public U call(Model model, T t) {
            Model target = parent.call(model, null);
            return expression.call(target, t);
        }

        public U tryCall(Model model, T t) throws ModelException {
            Model target = parent.call(model, null);
            return expression.tryCall(target, t);
        }

        public ModelFactory getModelFactory() {
            return expression.getModelFactory();
        }

        public List<String> asStrings() {
            List<String> list = new ArrayList<String>();
            list.addAll(parent.asStrings());
            list.addAll(expression.asStrings());
            return list;
        }

        @Override
        public String toString() {
            return parent + "" + expression;
        }
    }

    private static class KeyedObjectExpression<V> implements Expression<String, V> {
        private final Provider<KeyedObjectFactory<String, V>> provider;
        private final ModelFactory modelFactory;

        private KeyedObjectExpression(Provider<KeyedObjectFactory<String, V>> provider, ModelFactory modelFactory) {
            this.provider = provider;
            this.modelFactory = modelFactory;
        }

        public V call(Model model, String s) {
            return provider.get().create(s);
        }

        public V tryCall(Model model, String s) {
            return call(model, s);
        }

        public ModelFactory getModelFactory() {
            return modelFactory;
        }

        public List<String> asStrings() {
            return Arrays.asList("new " + modelFactory.getType());
        }
    }

    private static class PackedExpression<T> implements Expression<List<String>, T> {
        private final Expression<List<String>, Model> parent;
        private final Entry<T> entry;
        private final boolean isModel;
        private final int args;

        PackedExpression(Expression<Void, T> expression) {
            this(new ThisExpression<List<String>>(expression.getModelFactory()), Entry.create(expression), true, 0);
        }

        private PackedExpression(Expression<List<String>, Model> parent, Entry<T> entry, boolean isModel, int args) {
            this.parent = parent;
            this.entry = entry;
            this.isModel = isModel;
            this.args = args;
        }

        <M> PackedExpression<M> createChildWithArgument(Expression<String, M> expression) {
            return new PackedExpression<M>(castThis(), Entry.createWithArgument(expression), true, args + 1);
        }

        <M> PackedExpression<M> createChild(Expression<Void, M> expression) {
            return new PackedExpression<M>(castThis(), Entry.create(expression), true, args);
        }

        int getArgCount() {
            return args;
        }

        @SuppressWarnings("unchecked")
        private PackedExpression<Model> castThis() {
            if (!isModel) {
                throw new AssertionError();
            }
            return (PackedExpression<Model>) this;
        }

        public T call(Model root, List<String> strings) {
            Model model = parent.call(root, strings);
            Expression<String, T> expression = entry.asParametered();
            if (expression != null) {
                return expression.call(model, strings.get(args - 1));
            } else {
                return entry.get().call(model, null);
            }
        }

        public T tryCall(Model root, List<String> t) throws ModelException {
            throw new AssertionError();
        }

        public ModelFactory getModelFactory() {
            return entry.get().getModelFactory();
        }

        public List<String> asStrings() {
            List<String> list = new ArrayList<String>();
            list.addAll(parent.asStrings());
            list.addAll(entry.get().asStrings());
            return list;
        }

        String getLabel() {
            List<String> list = asStrings();
            return list.get(list.size() - 1);
        }

        @Override
        public String toString() {
            return parent + "" + entry;
        }

        private static class Entry<U> {
            private final boolean hasArgument;
            private final Expression<?, U> expression;

            Entry(Expression<?, U> expression, boolean hasArgument) {
                this.expression = expression;
                this.hasArgument = hasArgument;
            }

            static <U> Entry<U> create(Expression<Void, U> expression) {
                return new Entry<U>(expression, false);
            }

            static <U> Entry<U> createWithArgument(Expression<String, U> expression) {
                return new Entry<U>(expression, true);
            }

            @SuppressWarnings("unchecked")
            Expression<String, U> asParametered() {
                if (hasArgument) {
                    return (Expression<String, U>) expression;
                } else {
                    return null;
                }
            }

            @SuppressWarnings("unchecked")
            Expression<Void, U> get() {
                return (Expression<Void, U>) expression;
            }

            @Override
            public String toString() {
                return expression.toString();
            }
        }
    }

    static class ExpressionBuilder {
        private final ControllerBuilder builder;
        private final List<String> strings;

        ExpressionBuilder(ControllerBuilder builder, String string) {
            this(builder, Arrays.asList(string.split("\\.")));
        }

        ExpressionBuilder(ControllerBuilder builder, List<String> strings) {
            this.builder = builder;
            this.strings = strings;
        }

        private String tail() {
            return strings.get(strings.size() - 1);
        }

        private <T, U> Expression<T, U> create(Class<T> inputClass, Class<U> outputClass) throws NoSuchMethodException {
            return create(TypeLiteral.get(inputClass), TypeLiteral.get(outputClass), null);
        }

        private <T, U> Expression<T, U> create(TypeLiteral<T> inputType, TypeLiteral<U> outputType, ModelFactory child) throws NoSuchMethodException {
            Expression<Void, Model> expression = new ThisExpression<Void>(builder.getModelFactory());
            String s0 = null;
            for (String s : strings) {
                if (s0 != null) {
                    ModelFactory modelFactory = expression.getModelFactory();
                    Expression<Void, Model> getter = modelFactory.register(s0, TypeLiteral.get(void.class), TypeLiteral.get(Model.class), null);
                    expression = new ExpressionImpl<Void, Model>(expression, getter);
                }
                s0 = s;
            }
            ModelFactory modelFactory = expression.getModelFactory();
            Expression<T, U> call = modelFactory.register(tail(), inputType, outputType, child);
            return new ExpressionImpl<T, U>(expression, call);
        }

        <T> Expression<Void, T> asGetter(TypeLiteral<T> type) throws NoSuchMethodException {
            return create(TypeLiteral.get(void.class), type, null);
        }

        Expression<Void, String> asString() throws NoSuchMethodException {
            return create(void.class, String.class);
        }

        Expression<Void, Boolean> asBoolean() throws NoSuchMethodException {
            return create(void.class, boolean.class);
        }

        Expression<Void, Model> asChild() throws NoSuchMethodException {
            return create(TypeLiteral.get(void.class), new TypeLiteral<Model>() {
            }, null);
        }

        Expression<String, Model> asKeyedChild() throws NoSuchMethodException {
            return create(TypeLiteral.get(String.class), new TypeLiteral<Model>() {
            }, null);
        }

        Expression<String, Model> asKeyedChild(Expression<?, ?> alias) throws NoSuchMethodException {
            ModelFactory modelFactory = alias.getModelFactory();
            return create(TypeLiteral.get(String.class), new TypeLiteral<Model>() {
            }, modelFactory);
        }

        Expression<Void, Void> asAction() throws NoSuchMethodException {
            return create(void.class, void.class);
        }

        Expression<Void, Iterable<Map.Entry<String, Model>>> asMap(Expression<?, Model> otherExpression) throws NoSuchMethodException {
            ModelFactory modelFactory = otherExpression.getModelFactory();

            TypeLiteral<Iterable<Map.Entry<String, Model>>> returnType = new TypeLiteral<Iterable<Map.Entry<String, Model>>>() {
            };
            TypeLiteral<?> mapType = builder.getType(asChild());
            String entrySetGetter;
            if (Map.class.equals(mapType.getRawType())) {
                entrySetGetter = "entrySet";
            } else if ("com.google.common.collect.Multimap".equals(mapType.getRawType().getCanonicalName())) {
                entrySetGetter = "entries";
            } else {
                throw new RuntimeException("Could not be handled as Map: " + mapType);
            }
            List<String> list = new ArrayList<String>();
            list.addAll(strings);
            list.add(entrySetGetter);
            return new ExpressionBuilder(builder, list).create(TypeLiteral.get(void.class), returnType, modelFactory);
        }

        Expression<Renderer<String, Model>, Void> asRender() throws NoSuchMethodException {
            return create(new TypeLiteral<Renderer<String, Model>>() {
            }, TypeLiteral.get(void.class), null);
        }

        Expression<Renderer<String, String>, Void> asOptionRender() throws NoSuchMethodException {
            return create(new TypeLiteral<Renderer<String, String>>() {
            }, TypeLiteral.get(void.class), null);
        }

        Expression<Void, String[]> asStringsGetter() throws NoSuchMethodException {
            return create(void.class, String[].class);
        }

        Expression<String[], Void> asStringsSetter() throws NoSuchMethodException {
            return create(String[].class, void.class);
        }

        Expression<Void, Void> asSubmit() throws NoSuchMethodException {
            return create(void.class, void.class);
        }
    }

    static class ControllerBuilder {
        private final ControllerFactory controllerFactory;
        private final ControllerBuilder parent;
        private final PageFactory.Builder builder;
        private final PackedExpression<Model> packedExpression;

        private ControllerBuilder(PageFactory.Builder builder, ControllerFactory controllerFactory, ModelFactory modelFactory) {
            parent = null;
            this.builder = builder;
            this.controllerFactory = controllerFactory;
            packedExpression = new PackedExpression<Model>(new ThisExpression<Void>(modelFactory));
        }

        private ControllerBuilder(ControllerBuilder parent, PackedExpression<Model> packedExpression) {
            this.parent = parent;
            builder = parent.builder;
            controllerFactory = parent.controllerFactory;
            this.packedExpression = packedExpression;
        }

        ExpressionBuilder call(String expression) {
            return new ExpressionBuilder(this, expression);
        }

        ControllerBuilder getChild(Expression<Void, Model> expression) {
            PackedExpression<Model> childExpression = packedExpression.createChild(expression);
            return new ControllerBuilder(this, childExpression);
        }

        ControllerBuilder getChildWithKey(Expression<String, Model> expression) {
            PackedExpression<Model> childExpression = packedExpression.createChildWithArgument(expression);
            return new ControllerBuilder(this, childExpression);
        }

        Expression<String, Model> createEach(Expression<Void, Model> expression) throws NoSuchMethodException {
            ControllerBuilder keys = getChild(expression);

            Expression<Void, Model> nullKeyExpression = keys.call("nullKey").asChild();
            TypeLiteral<?> keyType = keys.getChild(nullKeyExpression).getType();
            Expression<Void, Model> keysNullValueExpression = keys.call("nullValue").asChild();
            TypeLiteral<?> valueType = keys.getChild(keysNullValueExpression).getType();

            builder.addKeyedObject(keyType, valueType);

            SimpleModelFactory childModelFactory = new SimpleModelFactory(valueType);
            Provider<KeyedObjectFactory<String, Model>> provider = builder.getKeyedObjectFactoryProvider(valueType, childModelFactory);
            return new KeyedObjectExpression<Model>(provider, childModelFactory);
        }

        String registerEach(Expression<String, Model> expression) throws NoSuchMethodException {
            ModelFactory childModelFactory = expression.getModelFactory();
            TypeLiteral<?> childType = childModelFactory.getType();
            PackedExpression<Model> childExpression = packedExpression.createChildWithArgument(expression);
            return controllerFactory.set(Each.class, new Each(childType), childExpression, childType.getRawType().getSimpleName());
        }

        ControllerBuilder registerUniqueChild(Expression<String, Model> expression) {
            ModelFactory childModelFactory = expression.getModelFactory();
            return new ControllerBuilder(builder, controllerFactory, childModelFactory);
        }

        Provider<KeyedObjectFactory<String, Model>> getKeyedObjectFactoryProvider(Expression<String, Model> expression) {
            ModelFactory childModelFactory = expression.getModelFactory();
            return builder.getKeyedObjectFactoryProvider(childModelFactory.getType(), childModelFactory);
        }

        String registerImport(Expression<Void, Model> expression) {
            TypeLiteral<?> typeLiteral = expression.getModelFactory().getType();
            PackedExpression<Model> child = packedExpression.createChild(expression);
            Import i = new Import(typeLiteral.getRawType());
            return controllerFactory.set(Import.class, i, child, child.getLabel());
        }

        Class<?> getTypeForImport(String key) {
            PathItem<Import> anImport = controllerFactory.get(Import.class, key);
            return anImport.get().getType();
        }

        Provider<PageFactory.Template> getTemplateForPage(Class<?> type) {
            return builder.getTemplateForPage(type);
        }

        Provider<PageFactory.Template> getTemplateForImport(Class<?> type) {
            return builder.getTemplateForImport(type);
        }

        void registerExport(Element element) throws ClassNotFoundException {
            builder.registerForExport(element);
        }

        private String tail(List<String> list) {
            return list.get(list.size() - 1);
        }

        String registerAction(Expression<Void, Void> expression) throws NoSuchMethodException {
            Action action = new Action(expression);
            return controllerFactory.set(Action.class, action, packedExpression, tail(expression.asStrings()));
        }

        String registerForm() {
            Form form;
            String secondKey = "wugFormKey{" + packedExpression.asStrings() + "}";
            if (controllerFactory.contains(Form.class, secondKey)) {
                form = controllerFactory.get(Form.class, secondKey).get();
            } else {
                form = new Form(getModelFactory());
                String s = controllerFactory.set(Form.class, form, packedExpression, secondKey);
                if (!secondKey.equals(s)) {
                    throw new AssertionError();
                }
            }
            return controllerFactory.set(Form.class, form, packedExpression, tail(packedExpression.asStrings()));
        }

        private PathItem<Form> getForm() {
            String secondKey = "wugFormKey{" + packedExpression.asStrings() + "}";
            if (controllerFactory.contains(Form.class, secondKey)) {
                return controllerFactory.get(Form.class, secondKey);
            }
            return parent.getForm();
        }

        int getControlDepth() {
            String secondKey = "wugFormKey{" + packedExpression.asStrings() + "}";
            if (controllerFactory.contains(Form.class, secondKey)) {
                return 0;
            } else {
                return parent.getControlDepth() + 1;
            }
        }

        String registerControl(Expression<Void, String[]> getter, Expression<String[], Void> setter, String label) throws NoSuchMethodException {
            return getForm().get().putControl(packedExpression, getter, setter, label);
        }

        String registerSubmit(Expression<Void, Void> expression, String label) throws NoSuchMethodException {
            return getForm().get().putPost(packedExpression, expression, label);
        }

        /*TODO: validate catch expressions at ControllerFactory.init() */
        <T> String registerCatch(String expression, TypeLiteral<?> exceptionType) throws NoSuchMethodException {
            List<String> strings = Arrays.asList(expression);
            ModelFactory childModelFactory = new SimpleModelFactory(exceptionType);
            Catch aCatch = new Catch(childModelFactory, exceptionType, strings);
            return controllerFactory.set(Catch.class, aCatch, packedExpression, aCatch.getLabel());
        }

        ControllerBuilder getCatch(String key) {
            Catch c = controllerFactory.get(Catch.class, key).get();
            Expression<Void, Model> expression = c.getExpression();
            return new ControllerBuilder(this, packedExpression.createChild(expression));
        }

        TypeLiteral<?> getType() {
            return packedExpression.getModelFactory().getType();
        }

        TypeLiteral<?> getType(Expression<?, Model> expression) {
            return expression.getModelFactory().getType();
        }

        ModelFactory getModelFactory() {
            return packedExpression.getModelFactory();
        }

        String registerId(Expression<Void, ?> expression) {
            return controllerFactory.set(ElementId.class, new ElementId(expression), packedExpression, tail(expression.asStrings()));
        }
    }


    Controller create() {
        return new Controller(this);
    }

    static class Controller {
        private final ControllerFactory controllerFactory;
        private final Map<String, List<ModelException>> exceptions = new HashMap<String, List<ModelException>>();

        Controller(ControllerFactory controllerFactory) {
            this.controllerFactory = controllerFactory;
        }

        void action(Model root, String string) {
            Retrieve<Action> retrieve = controllerFactory.retrieve(Action.class, Arrays.asList(string.split(":")));
            retrieve.get().action(retrieve.getModel(root));
        }

        boolean fillForm(Model root, WugRequest request) {
            String string = request.getPost();
            Retrieve<Form> retrieve = controllerFactory.retrieve(Form.class, Arrays.asList(string.split(":")));
            Form form = retrieve.get();
            boolean hasException = false;
            for (String controlKey : form.listControlKeys()) {
                for (Map.Entry<String, String[]> entry : request.getPostParameters(controlKey).entrySet()) {
                    Retrieve<Control> controlRetrieve = retrieve.getControl(Control.class, Arrays.asList(entry.getKey().split(":")));
                    Model controlModel = controlRetrieve.getModel(root);
                    Control control = controlRetrieve.get();
                    try {
                        control.getSetter().tryCall(controlModel, entry.getValue());
                    } catch (ModelException e) {
                        putException(controlRetrieve.getControlCatchKey(), e);
                        hasException = true;
                    }
                }
            }

            return hasException;
        }

        void submitForm(Model root, WugRequest request) {
            String string = request.getPost();
            Retrieve<Form> retrieve = controllerFactory.retrieve(Form.class, Arrays.asList(string.split(":")));
            Form form = retrieve.get();

            for (String formAction : form.listPostKeys()) {
                Map<String, String[]> postParameters = request.getPostParameters(formAction);
                if (postParameters.size() != 1) {
                    continue;
                }
                for (Map.Entry<String, String[]> entry : postParameters.entrySet()) {
                    Retrieve<Post> postRetrieve = retrieve.getControl(Post.class, Arrays.asList(entry.getKey().split(":")));
                    Model postModel = postRetrieve.getModel(root);
                    Post post = postRetrieve.get();
                    post.getPostExpression().call(postModel, null);
                }
            }
        }

        void putException(String key, ModelException exception) {
            List<ModelException> list = exceptions.get(key);
            if (list == null) {
                list = new ArrayList<ModelException>();
                exceptions.put(key, list);
            }
            list.add(exception);
        }

        List<ModelException> getExceptions(TypeLiteral<?> exceptionType, String string) {
            List<ModelException> result = new ArrayList<ModelException>();
            Retrieve<Catch> retrieve = controllerFactory.retrieve(Catch.class, Arrays.asList(string.split(":")));
            String s = retrieve.getCatchKey();
            List<ModelException> list = exceptions.get(s);
            if (list != null) {
                for (ModelException modelException : list) {
                    if (TypeLiteralUtil.isAssignable(modelException.getCause().getClass(), exceptionType)) {
                        result.add(modelException);
                    }
                }
            }
            return result;
        }
    }

    private static class PathAndArg<T> {
        private final PathAndArg<Model> parent;
        private final PackedExpression<T> expression;
        private final List<String> argList;
        private final boolean isModel;

        private PathAndArg(PathAndArg<Model> parent, PackedExpression<T> expression, List<String> argList, boolean isModel) {
            this.parent = parent;
            this.expression = expression;
            this.argList = argList;
            this.isModel = isModel;
        }

        static PathAndArg<Model> create(ModelFactory modelFactory) {
            PackedExpression<Model> expression = new PackedExpression<Model>(new ThisExpression<Void>(modelFactory));
            return new PathAndArg<Model>(null, expression, Collections.<String>emptyList(), true);
        }

        @SuppressWarnings("unchecked")
        PathAndArg<Model> createChild(PackedExpression<Model> expression, List<String> argList) {
            if (!isModel) {
                throw new AssertionError();
            }
            return new PathAndArg<Model>((PathAndArg<Model>) this, expression, argList, true);
        }

        PathAndArg<Model> createSibling(PackedExpression<Model> expression, List<String> argList) {
            return new PathAndArg<Model>(parent, expression, argList, true);
        }

        @SuppressWarnings("unchecked")
        PathAndArg<Model> createCasted(ModelFactory modelFactory) {
            if (!isModel) {
                throw new AssertionError();
            }
            PackedExpression<Model> childExpression = expression.createChild(new ThisExpression<Void>(modelFactory));
            return new PathAndArg<Model>(parent, childExpression, argList, true);
        }

        String getCatchKey(Expression<Void, ?> expression) {
            return toString() + ":" + expression.asStrings();
        }

        @SuppressWarnings("unchecked")
        private T retrieve(Model root) {
            if (parent == null) {
                if (!isModel) {
                    throw new AssertionError();
                }
                return (T) root;
            } else {
                Model model = parent.retrieve(root);
                return expression.call(model, argList);
            }
        }

        @Override
        public String toString() {
            if (parent == null) {
                return "";
            }
            return parent + ":" + expression.asStrings() + argList;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj instanceof PathAndArg) {
                PathAndArg that = (PathAndArg) obj;
                if (parent == null) {
                    return that.parent == null;
                }
                return parent.equals(that.parent) && expression.asStrings().equals(that.expression.asStrings()) && argList.equals(that.argList);

            }
            return false;
        }

        @Override
        public int hashCode() {
            return toString().hashCode();
        }
    }

    private <T> Retrieve<T> retrieve(Class<T> type, List<String> strings) {
        return retrieve0(type, PathAndArg.create(modelFactory), strings);
    }

    private <T> Retrieve<T> retrieve(Class<T> type, PathAndArg<Model> pathAndArg, List<String> strings) {
        return retrieve0(type, pathAndArg.createCasted(modelFactory), strings);
    }

    private <T> Retrieve<T> retrieve0(Class<T> type, PathAndArg<Model> pathAndArg, List<String> strings) {
        String key = strings.get(0);
        if (contains(type, key)) {
            PathItem<T> pathItem = get(type, key);
            PathAndArg<Model> child = pathItem.createPathAndArg(pathAndArg, strings);
            return new Retrieve<T>(pathItem, child);
        } else if (contains(Import.class, key)) {
            PathItem<Import> pathItem = get(Import.class, key);
            PathAndArg<Model> child = pathItem.createPathAndArg(pathAndArg, strings);
            Import anImport = pathItem.get();
            return anImport.exportedControllerFactory.retrieve(type, child, pathItem.getNextArgs(strings));
        } else if (contains(Each.class, key)) {
            PathItem<Each> pathItem = get(Each.class, key);
            PathAndArg<Model> child = pathItem.createPathAndArg(pathAndArg, strings);
            return retrieve0(type, child, pathItem.getNextArgs(strings));
        } else {
            throw new RuntimeException("Unknown key: " + key);
        }
    }

    private static class Retrieve<T> {
        private final PathItem<T> pathItem;
        private final PathAndArg<Model> pathAndArg;

        Retrieve(PathItem<T> pathItem, PathAndArg<Model> pathAndArg) {
            this.pathItem = pathItem;
            this.pathAndArg = pathAndArg;
        }

        T get() {
            return pathItem.get();
        }

        Model getModel(Model root) {
            return pathAndArg.retrieve(root);
        }

        <T> Retrieve<T> getControl(Class<T> type, List<String> strings) {
            Form form = Form.class.cast(get());
            PathItem<T> pathItem = form.get(type, strings.get(0));
            PathAndArg<Model> sibling = pathItem.createSibling(pathAndArg, strings);
            return new Retrieve<T>(pathItem, sibling);
        }

        String getControlCatchKey() {
            Control control = Control.class.cast(get());
            return pathAndArg.getCatchKey(control.getGetter());
        }

        String getCatchKey() {
            return pathAndArg.getCatchKey(Catch.class.cast(get()).getExpression());
        }
    }
}
